package com.fary.security.config.annotation.web.builders;

import com.fary.context.ApplicationContext;
import com.fary.context.ApplicationContextAware;
import com.fary.core.SpringException;
import com.fary.http.HttpMethod;
import com.fary.security.access.expression.SecurityExpressionHandler;
import com.fary.security.config.annotation.AbstractConfiguredSecurityBuilder;
import com.fary.security.config.annotation.ObjectPostProcessor;
import com.fary.security.config.annotation.SecurityBuilder;
import com.fary.security.config.annotation.web.AbstractRequestMatcherRegistry;
import com.fary.security.web.DefaultSecurityFilterChain;
import com.fary.security.web.FilterChainProxy;
import com.fary.security.web.FilterInvocation;
import com.fary.security.web.SecurityFilterChain;
import com.fary.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import com.fary.security.web.access.WebInvocationPrivilegeEvaluator;
import com.fary.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import com.fary.security.web.access.intercept.FilterSecurityInterceptor;
import com.fary.security.web.debug.DebugFilter;
import com.fary.security.web.firewall.HttpFirewall;
import com.fary.security.web.servlet.util.matcher.MvcRequestMatcher;
import com.fary.security.web.util.matcher.RequestMatcher;
import com.fary.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * WebSecurity 由 WebSecurityConfiguration 创建，用于创建 FilterChainProxy,
 * 这个 FilterChainProxy 也就是通常我们所说的 Spring Security Filter Chain (springSecurityFilterChain)。
 * springSecurityFilterChain 是一个 Servlet 过滤器 Filter, DelegatingFilterProxy 会把真正的安全处理逻辑代理给这个 Filter 。
 * 通过创建一个或者多个 WebSecurityConfigurer, 或者重写 WebSecurityConfigurerAdapter 的某些方法，
 * 可以对 WebSecurity 进行定制。
 *
 */
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements ApplicationContextAware {
    private final Log logger = LogFactory.getLog(getClass());

    // 记录开发人员通过类似下面例子语句指定忽略的URL :
    // webSecurity.ignoring().antMatchers("/images/**", "/favicon.ico")
    // 在该例子中，会在 ignoredRequests 添加两个元素，分别对应 /images/**, /favicon.ico
    private final List<RequestMatcher> ignoredRequests = new ArrayList<>();

    // 最终被构建目标对象FilterChainProxy管理的多个安全过滤器链 SecurityFilterChain 的构建器列表，
    // 每个构建器用于构建一个 SecurityFilterChain
    private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();

    private IgnoredRequestConfigurer ignoredRequestRegistry;

    private FilterSecurityInterceptor filterSecurityInterceptor;

    private HttpFirewall httpFirewall;

    private boolean debugEnabled;

    private WebInvocationPrivilegeEvaluator privilegeEvaluator;

    // 初始化缺省的Web安全表达式处理器
    private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();

    // 实际使用的Web安全表达式处理器,缺省设置为使用缺省的Web安全表达式处理器
    private SecurityExpressionHandler<FilterInvocation> expressionHandler = defaultWebSecurityExpressionHandler;

    private Runnable postBuildAction = () -> {
    };

    /**
     * Creates a new instance
     */
    public WebSecurity(ObjectPostProcessor<Object> objectPostProcessor) {
        super(objectPostProcessor);
    }

    /**
     *
     */
    public IgnoredRequestConfigurer ignoring() {
        return ignoredRequestRegistry;
    }

    /**
     * Allows customizing the {@link HttpFirewall}. The default is
     * {@link StrictHttpFirewall}.
     */
    public WebSecurity httpFirewall(HttpFirewall httpFirewall) {
        this.httpFirewall = httpFirewall;
        return this;
    }

    /**
     * 是否启用了调试模式，来自注解 @EnableWebSecurity 的属性 debug，缺省值 false
     */
    public WebSecurity debug(boolean debugEnabled) {
        this.debugEnabled = debugEnabled;
        return this;
    }

    /**
     *
     */
    public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
        this.securityFilterChainBuilders.add(securityFilterChainBuilder);
        return this;
    }

    /**
     * Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is not specified,
     * then a {@link DefaultWebInvocationPrivilegeEvaluator} will be created when
     * {@link #securityInterceptor(FilterSecurityInterceptor)} is non null.
     *
     * @param privilegeEvaluator the {@link WebInvocationPrivilegeEvaluator} to use
     * @return the {@link WebSecurity} for further customizations
     */
    public WebSecurity privilegeEvaluator(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
        this.privilegeEvaluator = privilegeEvaluator;
        return this;
    }

    /**
     * Set the {@link SecurityExpressionHandler} to be used. If this is not specified,
     * then a {@link DefaultWebSecurityExpressionHandler} will be used.
     *
     * @param expressionHandler the {@link SecurityExpressionHandler} to use
     * @return the {@link WebSecurity} for further customizations
     */
    public WebSecurity expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
        Assert.notNull(expressionHandler, "expressionHandler cannot be null");
        this.expressionHandler = expressionHandler;
        return this;
    }

    /**
     * Gets the {@link SecurityExpressionHandler} to be used.
     *
     * @return the {@link SecurityExpressionHandler} for further customizations
     */
    public SecurityExpressionHandler<FilterInvocation> getExpressionHandler() {
        return expressionHandler;
    }

    /**
     * Gets the {@link WebInvocationPrivilegeEvaluator} to be used.
     *
     * @return the {@link WebInvocationPrivilegeEvaluator} for further customizations
     */
    public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
        if (privilegeEvaluator != null) {
            return privilegeEvaluator;
        }
        return filterSecurityInterceptor == null ? null : new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor);
    }

    /**
     *
     */
    public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
        this.filterSecurityInterceptor = securityInterceptor;
        return this;
    }

    /**
     * Executes the Runnable immediately after the build takes place
     */
    public WebSecurity postBuildAction(Runnable postBuildAction) {
        this.postBuildAction = postBuildAction;
        return this;
    }

    /**
     * 各种配置信息已经搜集齐备，通过该方法执行构建过程，构建 Filter FilterChainProxy 实例并返回该 Filter
     */
    @Override
    protected Filter performBuild() throws Exception {
        Assert.state(!securityFilterChainBuilders.isEmpty(),
                () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
                        + "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
                        + "More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly");
        // 计算出要创建的过滤器链 SecurityFilterChain 的个数 :
        // ignoredRequests 中URL通配符的个数 + securityFilterChainBuilders元素的个数，
        // 这里每个 securityFilterChainBuilders 元素实际上是一个 HttpSecurity
        int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
        List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
        // 对于每个要忽略的URL通配符，构建一个 SecurityFilterChain 实例，使用的实现类为
        // DefaultSecurityFilterChain , 该实现类实例初始化时不包含任何过滤器，从而对给定的URL，可以达到不对其进行安全检查的目的
        for (RequestMatcher ignoredRequest : ignoredRequests) {
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
        }
        // 对每个 securityFilterChainBuilder 执行其构建过程，生成一个 securityFilterChain,
        // 这里每个 securityFilterChainBuilders 元素实际上是一个 HttpSecurity
        for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
        // 由多个 SecurityFilterChain securityFilterChains 构造一个 FilterChainProxy，这就是最终要构建的
        // Filter : FilterChainProxy : springSecurityFilterChain
        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
        if (httpFirewall != null) {
            filterChainProxy.setFirewall(httpFirewall);
        }
        // 对根据配置新建的 filterChainProxy 进行验证,
        // FilterChainProxy 的缺省验证器是一个 NullFilterChainValidator,相应的验证逻辑为空方法
        filterChainProxy.afterPropertiesSet();

        Filter result = filterChainProxy;
        if (debugEnabled) {
            result = new DebugFilter(filterChainProxy);
        }
        postBuildAction.run();
        return result;
    }

    /**
     * 嵌套类，用于注册 Spring Security 需要忽略的 RequestMatcher 实例
     */
    public final class MvcMatchersIgnoredRequestConfigurer extends IgnoredRequestConfigurer {
        private final List<MvcRequestMatcher> mvcMatchers;

        private MvcMatchersIgnoredRequestConfigurer(ApplicationContext context, List<MvcRequestMatcher> mvcMatchers) {
            super(context);
            this.mvcMatchers = mvcMatchers;
        }

        public IgnoredRequestConfigurer servletPath(String servletPath) {
            for (MvcRequestMatcher matcher : this.mvcMatchers) {
                matcher.setServletPath(servletPath);
            }
            return this;
        }
    }

    /**
     * Allows registering {@link RequestMatcher} instances that should be ignored by
     * Spring Security.
     *
     * @author Rob Winch
     * @since 3.2
     */
    public class IgnoredRequestConfigurer extends AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {

        private IgnoredRequestConfigurer(ApplicationContext context) {
            setApplicationContext(context);
        }

        @Override
        public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
            List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
            WebSecurity.this.ignoredRequests.addAll(mvcMatchers);
            return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(), mvcMatchers);
        }

        @Override
        public MvcMatchersIgnoredRequestConfigurer mvcMatchers(String... mvcPatterns) {
            return mvcMatchers(null, mvcPatterns);
        }

        @Override
        protected IgnoredRequestConfigurer chainRequestMatchers(List<RequestMatcher> requestMatchers) {
            WebSecurity.this.ignoredRequests.addAll(requestMatchers);
            return this;
        }

        /**
         * Returns the {@link WebSecurity} to be returned for chaining.
         */
        public WebSecurity and() {
            return WebSecurity.this;
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws SpringException {
        this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);

        try {
            this.defaultWebSecurityExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class));
        } catch (SpringException ignored) {
        }

        try {
            this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(PermissionEvaluator.class));
        } catch (SpringException ignored) {
        }

        this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
        try {
            this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
        } catch (SpringException ignored) {
        }
    }
}